
import cfca.sadk.algorithm.common.CBCParam;
import cfca.sadk.algorithm.common.Mechanism;
import cfca.sadk.algorithm.common.PKIException;
import cfca.sadk.algorithm.sm2.SM2PrivateKey;
import cfca.sadk.algorithm.sm2.SM2PublicKey;
import cfca.sadk.asn1.pkcs.PKCS12;
import cfca.sadk.asn1.pkcs.PKCS12_SM2;
import cfca.sadk.lib.crypto.JCrypto;
import cfca.sadk.lib.crypto.Session;
import cfca.sadk.lib.crypto.bcsoft.BCSoftLib;
import cfca.sadk.org.bouncycastle.asn1.ASN1InputStream;
import cfca.sadk.org.bouncycastle.asn1.ASN1Primitive;
import cfca.sadk.org.bouncycastle.asn1.ASN1Sequence;
import cfca.sadk.org.bouncycastle.asn1.DEROctetString;
import cfca.sadk.org.bouncycastle.util.encoders.Hex;
import cfca.sadk.system.FileHelper;
import cfca.sadk.util.*;
import cfca.sadk.util.Signature;
import cfca.sadk.x509.certificate.X509Cert;
import cfca.sadk.system.CompatibleConfig;
import cfca.sadk.org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey;
import cfca.sadk.system.Mechanisms;
import cfca.sadk.lib.crypto.hard.HardLib;
import cfca.sadk.org.bouncycastle.asn1.pkcs.CertificationRequest;
import cfca.sadk.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import cfca.sadk.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import cfca.sadk.org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
import cfca.sadk.org.bouncycastle.asn1.x500.X500Name;
import cfca.sadk.org.bouncycastle.asn1.*;
import cfca.sadk.algorithm.common.PKCSObjectIdentifiers;
import cfca.sadk.org.bouncycastle.pkcs.PKCS10CertificationRequest;
import cfca.sadk.system.SM2OutputFormat;

import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Objects;
import java.security.*;
import java.util.Random;
import java.security.Security;
import java.nio.charset.StandardCharsets;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Objects;
import java.math.BigInteger;
import java.io.*;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;


class CFCAUtil {
    private static final String deviceName = JCrypto.JSOFT_LIB;
    private static Session session = null;

    static {
      try {
          JCrypto jCrypto = JCrypto.getInstance();
          jCrypto.initialize(deviceName, null);
          session = jCrypto.openSession(deviceName);
      } catch (PKIException e) {
          e.printStackTrace();
      }
    }

    static String signMessageByP1SM2FileData(String message, byte[] sm2FileData, String certPwd) throws Exception {

      SM2PrivateKey userPriKey = KeyUtil.getPrivateKeyFromSM2(sm2FileData, certPwd);
      Signature signature = new Signature();
      byte[] base64P1SignedData = signature.p1SignMessage(Mechanism.SM3_SM2,
              message.getBytes(StandardCharsets.UTF_8), userPriKey, session);
      return new String(base64P1SignedData, StandardCharsets.UTF_8);
    }
    /**
     * SM2证书加密消息
     * SM2公钥加密
     *
     * @param message 待加密数据
     * @param certPath 公钥路径
     * @throws Exception
     */
    public static String encryptMessageBySM2(String message, String certPath) throws Exception {
        // InputStream input = Objects.requireNonNull(ClassUtils.getDefaultClassLoader()).getResourceAsStream(certPath);
        InputStream input = new FileInputStream(certPath);
        if (input == null) {
            input = new FileInputStream(certPath);
        }
        X509Cert cert = new X509Cert(input);
        PublicKey userPubKey = cert.getPublicKey();
        Mechanism mechanism = new Mechanism(Mechanism.SM2);
        byte[] encryptedData = EncryptUtil.encrypt(mechanism, userPubKey, message.getBytes(StandardCharsets.UTF_8), session);
        return new String(encryptedData);
    }
    static void buildSM2CSR() throws Exception {
        //dn规则CN=051固定 @+商户号 @+公司营业执照号 后面不变！
        String dn = "CN=051@10200074@91320508MAECXFBK8X@1,OU=Organizational-1,OU=Local RA,O=CFCA SM2 OCA31,C=CN";
        //密码
        String pwd = "xlmp666888";
        CFCAP10Request p10Request = new CFCAP10Request(session);
        Mechanism mechanism = new Mechanism(Mechanism.SM3_SM2);
        byte[] base64P10Data = p10Request.generateDoublePKCS10Request(mechanism, 256, dn,pwd);
        // 可以拿此申请文件数据到CA中申请双证
        System.out.println("[SM2申请P10]: " + new String(base64P10Data, StandardCharsets.UTF_8));
        // 签名密钥，用户需要安全保存（加密密钥）
        KeyPair keyPair = p10Request.getKeyPair();
        // System.out.println("[SM2签名私钥]: " + Base64.toBase64String(keyPair.getPrivate().getEncoded()));
        // System.out.println("[SM2签名公钥]: " + Base64.toBase64String(keyPair.getPublic().getEncoded()));
        // // 临时密钥，用户需要安全保存（加密密钥）
        // KeyPair temporaryKeyPair = p10Request.getTemporaryKeyPair();
        // System.out.println("[SM2加密私钥]: " + Base64.toBase64String(temporaryKeyPair.getPrivate().getEncoded()));
        // System.out.println("[SM2加密公钥]: " + Base64.toBase64String(temporaryKeyPair.getPublic().getEncoded()));

        // [SM2申请P10]: MIIB/DCCAZ8CAQAwgYAxKjAoBgNVBAMMITA1MUAxMDIwMDA3NEA5MTMyMDUwOE1BRUNYRkJLOFhAMTEZMBcGA1UECwwQT3JnYW5pemF0aW9uYWwtMTERMA8GA1UECwwITG9jYWwgUkExFzAVBgNVBAoMDkNGQ0EgU00yIE9DQTMxMQswCQYDVQQGEwJDTjBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABAj8rQQ8//+Fj4FB5AYX1UhkUzK3cEmbSYbtpbK0wEYDE2GoR/ucOt/VRFcqNIgo1jbztDQdbAufmPY+7xiwTDWggbswFwYJKoZIhvcNAQkHEwp4bG1wNjY2ODg4MIGfBgkqhkiG9w0BCT8EgZEwgY4CAQEEgYgAtAAAAAEAAE4B/Cw4eV0kjb4CbqnpRMj28cwM4q41pnLUqI6iaEdvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvOE94nhDPbncSjGpkl3kvDt7nCeQ0wNElz/GThGCgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAwGCCqBHM9VAYN1BQADSQAwRgIhAMEjCzE+Mjy4NuZ7YfbZkobUhP260+E5qmYFa1xOFD25AiEAti92634ZoLVk6Z1sm2R09AA8H+I1aUOI289nhkcuCsE=
        // [SM2签名私钥]: MIGIAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG4wbAIBAQIhALI5U7esOHhS3F1ONewzZtnHofwKJSbKb3bE+Ie7y00ToUQDQgAECPytBDz//4WPgUHkBhfVSGRTMrdwSZtJhu2lsrTARgMTYahH+5w639VEVyo0iCjWNvO0NB1sC5+Y9j7vGLBMNQ==
        // [SM2签名公钥]: MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAECPytBDz//4WPgUHkBhfVSGRTMrdwSZtJhu2lsrTARgMTYahH+5w639VEVyo0iCjWNvO0NB1sC5+Y9j7vGLBMNQ==
        // [SM2加密私钥]: MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQIgXmTeRp6YPtWTpXG2QM/RxigYKgyelx5lu6oktQ+cO4GhRANCAAROAfwsOHldJI2+Am6p6UTI9vHMDOKuNaZy1KiOomhHby84T3ieEM9udxKMamSXeS8O3ucJ5DTA0SXP8ZOEYKCg
        // [SM2加密公钥]: MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAETgH8LDh5XSSNvgJuqelEyPbxzAzirjWmctSojqJoR28vOE94nhDPbncSjGpkl3kvDt7nCeQ0wNElz/GThGCgoA==

        // // 可以拿此申请文件数据到CA中申请双证
        // log.info("[SM2申请P10]: " + new String(base64P10Data, StandardCharsets.UTF_8));
        // // 签名密钥，用户需要安全保存（加密密钥）
        // log.info("[SM2签名私钥]: " + Base64.toBase64String(keyPair.getPrivate().getEncoded()));
        // log.info("[SM2签名公钥]: " + Base64.toBase64String(keyPair.getPublic().getEncoded()));
        // // 临时密钥，用户需要安全保存（加密密钥）
        // log.info("[SM2加密私钥]: " + Base64.toBase64String(temporaryKeyPair.getPrivate().getEncoded()));
        // log.info("[SM2加密公钥]: " + Base64.toBase64String(temporaryKeyPair.getPublic().getEncoded()));
    }
    static void generateSM2File() throws Exception {
        //signatureCert 商户后台返回的签名公钥证书值
        String certData = "MIIDTDCCAvCgAwIBAgIFSRkwUoUwDAYIKoEcz1UBg3UFADBcMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRswGQYDVQQDDBJDRkNBIEFDUyBTTTIgT0NBMzEwHhcNMjUwNDE1MTQzNDU0WhcNMzAwNDE1MTQzNDU0WjB/MQswCQYDVQQGEwJDTjEXMBUGA1UECgwOQ0ZDQSBBQ1MgT0NBMzExDzANBgNVBAsMBjk1ZXBheTEZMBcGA1UECwwQT3JnYW5pemF0aW9uYWwtMTErMCkGA1UEAwwiMDUxQDEwMjAwMDc0QDg5MTMyMDUwOE1BRUNYRkJLOFhAMTBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABAj8rQQ8//+Fj4FB5AYX1UhkUzK3cEmbSYbtpbK0wEYDE2GoR/ucOt/VRFcqNIgo1jbztDQdbAufmPY+7xiwTDWjggF4MIIBdDBsBggrBgEFBQcBAQRgMF4wKAYIKwYBBQUHMAGGHGh0dHA6Ly9vY3NwLmNmY2EuY29tLmNuL29jc3AwMgYIKwYBBQUHMAKGJmh0dHA6Ly9jcmwuY2ZjYS5jb20uY24vb2NhMzEvb2NhMzEuY2VyMB8GA1UdIwQYMBaAFAjY0SbESH2c7KyY6fF/YrmAzqlFMAwGA1UdEwEB/wQCMAAwSAYDVR0gBEEwPzA9BghggRyG7yoBBDAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNuL3VzL3VzLTE0Lmh0bTA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLmNmY2EuY29tLmNuL29jYTMxL1NNMi9jcmw0NjYxLmNybDAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0OBBYEFH+Td8JWZPn0qc90A/MNngY2fgsqMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAMBggqgRzPVQGDdQUAA0gAMEUCIBHIHLenNxygF4EQ/l54Kdoa/jbtVUWhNe5mjJG9Of/VAiEAidU0NQekLyo7+kwIDiKgCLUsv6iePYukYoQ6K1hOZts=";
        X509Cert x509Cert = new X509Cert(certData.getBytes());
        //[SM2签名私钥]
        String encryptionPrivateKey = "MIGIAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG4wbAIBAQIhALI5U7esOHhS3F1ONewzZtnHofwKJSbKb3bE+Ie7y00ToUQDQgAECPytBDz//4WPgUHkBhfVSGRTMrdwSZtJhu2lsrTARgMTYahH+5w639VEVyo0iCjWNvO0NB1sC5+Y9j7vGLBMNQ==";
        // PrivateKey privateKey = SM2PrivateKey.getInstance(Base64.decode(encryptionPrivateKey));
        // //私钥密码
        // String password = "xlmp666888";
        // String fileName = "/Users/wjc/git/cmall/cmall_server/apps/pay/lzpay/cert/sm/4919305285_SM2_PrivateKey.sm2";
        // System.out.println("参数x509Cert：" + x509Cert);
        // System.out.println("参数privateKey：" + privateKey);
        // System.out.println("参数password：" + password);
        // System.out.println("参数fileName：" + fileName);
        // PKCS12_SM2.generateSM2File(x509Cert, privateKey, password, fileName);

    }
}

public class Main {
    /**
     * 批量代付接口
     *
     * @param args
     * @throws Exception
     */
    
    public static void main(String[] args) throws Exception  {// 签名类型
      String pubKeyPath = "";
      String pfxPath = "";
      String certPwd = "";
      String data = "";
      if(args.length>1){
        if(args[0].equals("sign")){
          pfxPath = args[1];
          certPwd = args[2];
          data = args[3];
          // System.out.println("data1：" + data + pfxPath + certPwd);

          InputStream input = new FileInputStream(pfxPath);
        // System.out.println("input：" + input);
        
      
          ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
          byte[] buffer = new byte[1024];
          int n = 0;
          while (-1 != (n = input.read(buffer))) {
              outputStream.write(buffer, 0, n);
          }
          outputStream.flush();
          // System.out.println("outputStream：" + outputStream);

          String sign = CFCAUtil.signMessageByP1SM2FileData(data, outputStream.toByteArray(), certPwd);
          System.out.println(sign);
        }else if(args[0].equals("encrypt")){
          pubKeyPath = args[1];
          String encrypt_data1 = args[2];
          String encrypt_data2 = args[3];
          String envKey = CryptoUtil.buildEnvKey("SM2");
          String envContent = CryptoUtil.buildEnvContent(envKey, "SM2");
          String[] envContentArr = envContent.split("\\|");
          encrypt_data1 = CryptoUtil.encrypt(encrypt_data1, envContentArr[1], envContentArr[0]);
          encrypt_data2 = CryptoUtil.encrypt(encrypt_data2, envContentArr[1], envContentArr[0]);//数字信封
          //数字信封
          String digitalEnvelope = CryptoUtil.buildDgtlEnvlp(envKey, pubKeyPath, "SM2");
          System.out.println(encrypt_data1);
          System.out.println(encrypt_data2);
          System.out.println(digitalEnvelope);
        }else{
          System.out.println("参数缺失");
        }
        
      }else{
        pubKeyPath = "/Users/wjc/git/cmall/cmall_server/apps/pay/lzpay/cert/sm/4919305285_SM2_SignCert.cer";
        pfxPath = "/Users/wjc/git/cmall/cmall_server/apps/pay/lzpay/cert/sm/4919305285_SM2_PrivateKey.sm2";
        certPwd = "xlmp666888";
        // 签名类型
        String signType = "SM2";
        // 序列号
        String signSN = "4776575245";
        // 加密序列号
        String encryptSN = "4776575245";
        // 平台账户
        String merchantNo = "10200074";
        // 账户号
        String subMerchantNo = "10200074";
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("merchantNo", merchantNo);
        map.put("subMerchantNo", subMerchantNo);

        map.put("signType", signType);
        map.put("signSN", signSN);
        map.put("encryptSN", encryptSN);
        data = signParamSplicing(map);
        // CFCAUtil.buildSM2CSR();  //申请p10
        // CFCAUtil.generateSM2File();  //生成私钥证书
        String contactIdNo = "412702198910114572";
        String contactPhone = "13122866992";
        String envKey = CryptoUtil.buildEnvKey("SM2");
        String envContent = CryptoUtil.buildEnvContent(envKey, "SM2");
        // System.out.println("envContent:" + envContent);
        String[] envContentArr = envContent.split("\\|");
        // System.out.println("envContentArr:" + envContentArr +";"+ envContentArr[0]+";"+ envContentArr[1]+";"+contactIdNo+";"+contactPhone);
        contactIdNo = CryptoUtil.encrypt(contactIdNo, envContentArr[1], envContentArr[0]);
        contactPhone = CryptoUtil.encrypt(contactPhone, envContentArr[1], envContentArr[0]);
        //数字信封
        String digitalEnvelope = CryptoUtil.buildDgtlEnvlp(envKey, pubKeyPath, signType);
        System.out.println(contactIdNo);
        System.out.println(contactPhone);
        System.out.println(digitalEnvelope);

      }

    }


private static String signParamSplicing(Map<String, Object> paramMap) {
      
      Set<String> keySet = paramMap.keySet();
      String[] keyArray = keySet.toArray(new String[0]);
      Arrays.sort(keyArray);
      StringBuilder data = new StringBuilder();
      for (String paramKey : keyArray) {
          if ("sign".equals(paramKey)) {
              continue;
          }
          Object value = paramMap.get(paramKey);
          if (value != null && !"".equals(value) && !"null".equals(value)) {
              data.append(paramKey).append("=").append(value).append("&");
          }
      }
      return data.deleteCharAt(data.length() - 1).toString();
  }
}


class CFCAP10Request {
   private String subject = null;
    private PublicKey publicKey = null;
    private ASN1Set attributes = null;
    private PublicKey tempPublicKey = null;
    private KeyPair keyPair = null;
    private KeyPair tempKeyPair;
    private Session session = null;
    private boolean needVerify = false;
    private boolean p10RequestVerifyState = false;
    private int certReqType = 0;
    Mechanism mechanism = null;
    byte[] signature = null;
    byte[] source = null;
    private int formatSignedBytes;

    /** @deprecated */
    public CFCAP10Request() {
        this.formatSignedBytes = CompatibleConfig.P10RequestFormatSignedBytes;
    }

    public CFCAP10Request(Session session) {
        this.formatSignedBytes = CompatibleConfig.P10RequestFormatSignedBytes;
        this.session = session;
    }

    /** @deprecated */
    public void setFormatSignedBytes(int formatSignedBytes) {
        this.formatSignedBytes = formatSignedBytes;
    }

    public int getKeySize() {
        if (Mechanisms.isSM2WithSM3(this.mechanism)) {
            return 256;
        } else {
            BCRSAPublicKey bcPublicKey = (BCRSAPublicKey)this.publicKey;
            BigInteger n = bcPublicKey.getModulus();
            return n.bitLength();
        }
    }

    public ASN1Set getAttributes() {
        return this.attributes;
    }

    public PublicKey getPublicKey() {
        return this.publicKey;
    }

    public String getSubject() {
        return this.subject;
    }
    public byte[] generateDoublePKCS10Request(Mechanism mechanism, int keyLength, Session session, String dn,String pwd) throws PKIException {
        this.session = session;
        return this.generateDoublePKCS10Request(mechanism, keyLength, dn, pwd);
    }
    private final byte[] sm2FormatSignedBytes(byte[] signedBytes) throws IOException {
        return this.formatSignedBytes == 2 ? SM2OutputFormat.sm2FormatSigned64Bytes(signedBytes) : SM2OutputFormat.sm2FormatSignedBytes(signedBytes);
    }
    public byte[] generateDoublePKCS10Request(Mechanism mechanism, int keyLength, String dn,String pwd) throws PKIException {
        String signAlgValue = mechanism.getMechanismType();
        Mechanism enMechanism = Mechanisms.encryptMechanismFrom(mechanism);
        if (null == enMechanism) {
            throw new PKIException("unsupported algorithm: " + mechanism.getMechanismType());
        } else {
            enMechanism.setParam(mechanism.getParam());
            if (null == this.session) {
                throw new PKIException("encryption session uninitialized!");
            } else {
                this.keyPair = KeyUtil.generateKeyPair(enMechanism, keyLength, this.session);
                if (this.session instanceof HardLib) {
                    this.tempKeyPair = KeyUtil.generateKeyPair(enMechanism, keyLength, new BCSoftLib());
                } else {
                    this.tempKeyPair = KeyUtil.generateKeyPair(enMechanism, keyLength, this.session);
                }

                SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(this.keyPair.getPublic().getEncoded());
                SubjectPublicKeyInfo temporaryPublicKeyInfo = SubjectPublicKeyInfo.getInstance(this.tempKeyPair.getPublic().getEncoded());
                X500Name subject = new X500Name(dn);
                ASN1EncodableVector challengePasswordVector = new ASN1EncodableVector();
                DERPrintableString challengePasswordValue = new DERPrintableString(pwd == null ? "111111" : pwd);
                challengePasswordVector.add(PKCSObjectIdentifiers.pkcs_9_at_challengePassword);
                challengePasswordVector.add(challengePasswordValue);
                DERSequence challengePasswordSeq = new DERSequence(challengePasswordVector);
                ASN1EncodableVector tempPublicKeyVector = new ASN1EncodableVector();
                tempPublicKeyVector.add(PKCSObjectIdentifiers.pkcs_9_at_tempPublicKey);
                ASN1Integer derInt = new ASN1Integer(1L);
                DEROctetString tempOctetString = null;
                if (Mechanisms.isRSAType(enMechanism)) {
                    try {
                        tempOctetString = new DEROctetString(temporaryPublicKeyInfo.parsePublicKey());
                    } catch (IOException var29) {
                        throw new PKIException("generateDoublePKCS10Request Failure", var29);
                    }
                } else if (Mechanisms.isSM2Type(enMechanism)) {
                    byte[] tempPublicKeyData = new byte[136];
                    byte[] temporaryPublicKeyData = temporaryPublicKeyInfo.getPublicKeyData().getBytes();
                    byte[] temporaryPublicKeyX = new byte[32];
                    byte[] temporaryPublicKeyY = new byte[32];
                    System.arraycopy(temporaryPublicKeyData, 1, temporaryPublicKeyX, 0, 32);
                    System.arraycopy(temporaryPublicKeyData, 33, temporaryPublicKeyY, 0, 32);
                    byte[] CONST1 = new byte[]{0, -76, 0, 0};
                    byte[] CONST2 = new byte[]{0, 1, 0, 0};
                    System.arraycopy(CONST1, 0, tempPublicKeyData, 0, 4);
                    System.arraycopy(CONST2, 0, tempPublicKeyData, 4, 4);
                    System.arraycopy(temporaryPublicKeyX, 0, tempPublicKeyData, 8, 32);
                    System.arraycopy(temporaryPublicKeyY, 0, tempPublicKeyData, 72, 32);
                    tempOctetString = new DEROctetString(tempPublicKeyData);
                }

                ASN1EncodableVector intAndPublicKeyVector = new ASN1EncodableVector();
                intAndPublicKeyVector.add(derInt);
                intAndPublicKeyVector.add(tempOctetString);
                DERSequence tempPublicKeyInfoSeq = new DERSequence(intAndPublicKeyVector);
                DEROctetString intAndPublicKeyOctetString = null;

                try {
                    intAndPublicKeyOctetString = new DEROctetString(tempPublicKeyInfoSeq);
                } catch (IOException var28) {
                    throw new PKIException("generateDoublePKCS10Request Failure", var28);
                }

                tempPublicKeyVector.add(intAndPublicKeyOctetString);
                DERSequence tempPubKeySeq = new DERSequence(tempPublicKeyVector);
                ASN1EncodableVector chaPasswordAndPubKeyVector = new ASN1EncodableVector();
                chaPasswordAndPubKeyVector.add(challengePasswordSeq);
                chaPasswordAndPubKeyVector.add(tempPubKeySeq);
                this.attributes = new DERSet(chaPasswordAndPubKeyVector);
                CertificationRequestInfo certRequestInfo = new CertificationRequestInfo(subject, publicKeyInfo, this.attributes);
                Object obj = Mechanism.getObjectIdentifier(signAlgValue);
                AlgorithmIdentifier signAlg = new AlgorithmIdentifier(ASN1ObjectIdentifier.getInstance(obj), DERNull.INSTANCE);

                try {
                    byte[] sourceData = certRequestInfo.getEncoded("DER");
                    byte[] signature = this.session.sign(mechanism, this.keyPair.getPrivate(), sourceData);
                    if (Mechanisms.isSM2WithSM3(signAlgValue)) {
                        signature = this.sm2FormatSignedBytes(signature);
                    }

                    CertificationRequest certRequest = new CertificationRequest(certRequestInfo, signAlg, new DERBitString(signature));
                    PKCS10CertificationRequest p10 = new PKCS10CertificationRequest(certRequest);
                    // return Base64.encode(p10.getEncoded());
                    return sourceData;
                } catch (PKIException var26) {
                    throw var26;
                } catch (Exception var27) {
                    throw new PKIException("Generated P10 Failure", var27);
                }
            }
        }
    }
    public KeyPair getKeyPair() {
      return this.keyPair;
    }
    public KeyPair getTemporaryKeyPair() {
      return this.tempKeyPair;
    }
}
/**
 * 加密、解密工具类
 */
class CryptoUtil {
  
    private static final String ALGORITHM_NAME = "SM4";
    public static final String SM4_ECB_PKCS7PADDING = "SM4/ECB/PKCS7Padding";
    static {
      Security.insertProviderAt(new BouncyCastleProvider(), 1);
    }
    /**
     * 数字信封key
     *
     * @param certType 证书类型
     * @return
     */
    public static String buildEnvKey(String certType) {
        if ("RSA".equals(certType)) {
            return RandomStrUtils.randomAlphanumeric(32);
        } else {
            return RandomStrUtils.randomAlphanumeric(16);
        }
    }

        /**
     * 生成数字信封内容
     *
     * @param envKey   数字信封key
     * @param certType 证书类型
     * @return
     */
    public static String buildEnvContent(String envKey, String certType) {
        if ("RSA".equals(certType)) {
            return "01|" + envKey;
        } else {
            return "02|" + envKey;
        }
    }
        /**
     * 生成数字信封
     *
     * @param publicKey 证书
     * @param data      加密数据
     * @param certType  证书类型
     * @return
     */
    public static String buildDgtlEnvlp(String data, String publicKey, String certType) {
        String keyContent = buildEnvContent(data, certType);
        return encryptByPubKey(keyContent, publicKey, certType);
    }
        /**
     * 证书加密
     *
     * @param data      加密数据
     * @param pubKeyPath 证书路径
     * @param certType  证书类型
     * @return
     */
    public static String encryptByPubKey(String data, String pubKeyPath, String certType) {
        try {
            return CFCAUtil.encryptMessageBySM2(data, pubKeyPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

        /**
     * 敏感信息加密
     *
     * @param data     加密数据
     * @param key      key
     * @param certType 证书类型
     * @return
     */
    // public static byte[] encrypt(String data, byte[] key, String certType) {
    //     return SM4Utils.encryptEcb(key, data);
    // }

    /**
     * 敏感信息加密
     *
     * @param data     加密数据
     * @param key      key
     * @param certType 证书类型
     * @return
     */
    public static String encrypt(String data, String key, String certType) {

        byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
        // System.out.println("加密后的数据keyByte：" + keyByte);
        byte[] dataByte = encryptEcb(keyByte, data);
        // System.out.println("加密后的数据dataByte：" + dataByte);
        // System.out.println("加密后的数据：" + Base64.getEncoder().encodeToString(dataByte));
        return Base64.getEncoder().encodeToString(dataByte);
    }

    // public static String encryptEcb(String key, String data) {
    //     byte[] encryptBytes = encryptEcb(key.getBytes(StandardCharsets.UTF_8), data, StandardCharsets.UTF_8.name());
    //     System.out.println("加密后的数据encryptBytes：" + encryptBytes);
    //     return Base64.getEncoder().encodeToString(encryptBytes);
    // }
    
    /**
     * sm4加密
     * @explain 加密模式：ECB 密文长度不固定，会随着被加密字符串长度的变化而变化
     * @param key 密钥（忽略大小写）
     * @param data 待加密字符串
     * @return 加密字符串
     */
    public static byte[] encryptEcb(byte[] key, String data) {
        return encryptEcb(key, data, StandardCharsets.UTF_8.name());
    }
        /**
     * sm4加密
     * @explain 加密模式：ECB 密文长度不固定，会随着被加密字符串长度的变化而变化
     * @param key 16进制密钥（忽略大小写）
     * @param data 待加密字符串
     * @param charset 编码
     * @return 加密字符串
     */
    public static byte[] encryptEcb(byte[] key, String data, String charset) {
        try {
            byte[] srcData = data.getBytes(charset);

            // System.out.println("srcData：" + srcData);
            return encrypt_Ecb_Padding(key, srcData);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

        /**
     * 加密模式之Ecb
     * @param key
     * @param data
     * @return
     */
  

    public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) {
        try {
            // System.out.println("key:" + SM4_ECB_PKCS7PADDING + ";"+ BouncyCastleProvider.PROVIDER_NAME);
            Cipher cipher = Cipher.getInstance(SM4_ECB_PKCS7PADDING, BouncyCastleProvider.PROVIDER_NAME);
            // System.out.println("cipher：" + cipher);
            SecretKeySpec sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
            cipher.init(Cipher.ENCRYPT_MODE, sm4Key);
            return cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}


/**
 * @author huanxl
 * @date 2023/5/9 18:26
 */
class RandomStrUtils {
    private static final Random RANDOM = new SecureRandom();

    /**
     * <p>{@code RandomStrUtils} instances should NOT be constructed in
     * standard programming. Instead, the class should be used as
     * {@code RandomStrUtils.random(5);}.</p>
     *
     * <p>This constructor is public to permit tools that require a JavaBean instance
     * to operate.</p>
     */
    public RandomStrUtils() {
        super();
    }

    // Random
    //-----------------------------------------------------------------------
    /**
     * <p>Creates a random string whose length is the number of characters
     * specified.</p>
     *
     * <p>Characters will be chosen from the set of all characters.</p>
     *
     * @param count  the length of random string to create
     * @return the random string
     */
    public static String random(final int count) {
        return random(count, false, false);
    }

    /**
     * <p>Creates a random string whose length is the number of characters
     * specified.</p>
     *
     * <p>Characters will be chosen from the set of characters whose
     * ASCII value is between {@code 32} and {@code 126} (inclusive).</p>
     *
     * @param count  the length of random string to create
     * @return the random string
     */
    public static String randomAscii(final int count) {
        return random(count, 32, 127, false, false);
    }

    /**
     * <p>Creates a random string whose length is between the inclusive minimum and
     * the exclusive maximum.</p>
     *
     * <p>Characters will be chosen from the set of characters whose
     * ASCII value is between {@code 32} and {@code 126} (inclusive).</p>
     *
     * @param minLengthInclusive the inclusive minimum length of the string to generate
     * @param maxLengthExclusive the exclusive maximum length of the string to generate
     * @return the random string
     * @since 3.5
     */
    public static String randomAscii(final int minLengthInclusive, final int maxLengthExclusive) {
        return randomAscii(RandomUtil.nextInt(minLengthInclusive, maxLengthExclusive));
    }

    /**
     * <p>Creates a random string whose length is the number of characters
     * specified.</p>
     *
     * <p>Characters will be chosen from the set of Latin alphabetic
     * characters (a-z, A-Z).</p>
     *
     * @param count  the length of random string to create
     * @return the random string
     */
    public static String randomAlphabetic(final int count) {
        return random(count, true, false);
    }

    /**
     * <p>Creates a random string whose length is between the inclusive minimum and
     * the exclusive maximum.</p>
     *
     * <p>Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z).</p>
     *
     * @param minLengthInclusive the inclusive minimum length of the string to generate
     * @param maxLengthExclusive the exclusive maximum length of the string to generate
     * @return the random string
     * @since 3.5
     */
    public static String randomAlphabetic(final int minLengthInclusive, final int maxLengthExclusive) {
        return randomAlphabetic(RandomUtil.nextInt(minLengthInclusive, maxLengthExclusive));
    }

    /**
     * <p>Creates a random string whose length is the number of characters
     * specified.</p>
     *
     * <p>Characters will be chosen from the set of Latin alphabetic
     * characters (a-z, A-Z) and the digits 0-9.</p>
     *
     * @param count  the length of random string to create
     * @return the random string
     */
    public static String randomAlphanumeric(final int count) {
        return random(count, true, true);
    }

    /**
     * <p>Creates a random string whose length is between the inclusive minimum and
     * the exclusive maximum.</p>
     *
     * <p>Characters will be chosen from the set of Latin alphabetic
     * characters (a-z, A-Z) and the digits 0-9.</p>
     *
     * @param minLengthInclusive the inclusive minimum length of the string to generate
     * @param maxLengthExclusive the exclusive maximum length of the string to generate
     * @return the random string
     * @since 3.5
     */
    public static String randomAlphanumeric(final int minLengthInclusive, final int maxLengthExclusive) {
        return randomAlphanumeric(RandomUtil.nextInt(minLengthInclusive, maxLengthExclusive));
    }

    /**
     * <p>Creates a random string whose length is the number of characters specified.</p>
     *
     * <p>Characters will be chosen from the set of characters which match the POSIX [:graph:]
     * regular expression character class. This class contains all visible ASCII characters
     * (i.e. anything except spaces and control characters).</p>
     *
     * @param count  the length of random string to create
     * @return the random string
     * @since 3.5
     */
    public static String randomGraph(final int count) {
        return random(count, 33, 126, false, false);
    }

    /**
     * <p>Creates a random string whose length is between the inclusive minimum and
     * the exclusive maximum.</p>
     *
     * <p>Characters will be chosen from the set of \p{Graph} characters.</p>
     *
     * @param minLengthInclusive the inclusive minimum length of the string to generate
     * @param maxLengthExclusive the exclusive maximum length of the string to generate
     * @return the random string
     * @since 3.5
     */
    public static String randomGraph(final int minLengthInclusive, final int maxLengthExclusive) {
        return randomGraph(RandomUtil.nextInt(minLengthInclusive, maxLengthExclusive));
    }

    /**
     * <p>Creates a random string whose length is the number of characters
     * specified.</p>
     *
     * <p>Characters will be chosen from the set of numeric
     * characters.</p>
     *
     * @param count  the length of random string to create
     * @return the random string
     */
    public static String randomNumeric(final int count) {
        return random(count, false, true);
    }

    /**
     * <p>Creates a random string whose length is between the inclusive minimum and
     * the exclusive maximum.</p>
     *
     * <p>Characters will be chosen from the set of \p{Digit} characters.</p>
     *
     * @param minLengthInclusive the inclusive minimum length of the string to generate
     * @param maxLengthExclusive the exclusive maximum length of the string to generate
     * @return the random string
     * @since 3.5
     */
    public static String randomNumeric(final int minLengthInclusive, final int maxLengthExclusive) {
        return randomNumeric(RandomUtil.nextInt(minLengthInclusive, maxLengthExclusive));
    }

    /**
     * <p>Creates a random string whose length is the number of characters specified.</p>
     *
     * <p>Characters will be chosen from the set of characters which match the POSIX [:print:]
     * regular expression character class. This class includes all visible ASCII characters and spaces
     * (i.e. anything except control characters).</p>
     *
     * @param count  the length of random string to create
     * @return the random string
     * @since 3.5
     */
    public static String randomPrint(final int count) {
        return random(count, 32, 126, false, false);
    }

    /**
     * <p>Creates a random string whose length is between the inclusive minimum and
     * the exclusive maximum.</p>
     *
     * <p>Characters will be chosen from the set of \p{Print} characters.</p>
     *
     * @param minLengthInclusive the inclusive minimum length of the string to generate
     * @param maxLengthExclusive the exclusive maximum length of the string to generate
     * @return the random string
     * @since 3.5
     */
    public static String randomPrint(final int minLengthInclusive, final int maxLengthExclusive) {
        return randomPrint(RandomUtil.nextInt(minLengthInclusive, maxLengthExclusive));
    }

    /**
     * <p>Creates a random string whose length is the number of characters
     * specified.</p>
     *
     * <p>Characters will be chosen from the set of alpha-numeric
     * characters as indicated by the arguments.</p>
     *
     * @param count  the length of random string to create
     * @param letters  if {@code true}, generated string may include
     *  alphabetic characters
     * @param numbers  if {@code true}, generated string may include
     *  numeric characters
     * @return the random string
     */
    public static String random(final int count, final boolean letters, final boolean numbers) {
        return random(count, 0, 0, letters, numbers);
    }

    /**
     * <p>Creates a random string whose length is the number of characters
     * specified.</p>
     *
     * <p>Characters will be chosen from the set of alpha-numeric
     * characters as indicated by the arguments.</p>
     *
     * @param count  the length of random string to create
     * @param start  the position in set of chars to start at
     * @param end  the position in set of chars to end before
     * @param letters  if {@code true}, generated string may include
     *  alphabetic characters
     * @param numbers  if {@code true}, generated string may include
     *  numeric characters
     * @return the random string
     */
    public static String random(final int count, final int start, final int end, final boolean letters, final boolean numbers) {
        return random(count, start, end, letters, numbers, null, RANDOM);
    }

    /**
     * <p>Creates a random string based on a variety of options, using
     * default source of randomness.</p>
     *
     * <p>This method has exactly the same semantics as
     * {@link #random(int,int,int,boolean,boolean,char[],Random)}, but
     * instead of using an externally supplied source of randomness, it uses
     * the internal static {@link Random} instance.</p>
     *
     * @param count  the length of random string to create
     * @param start  the position in set of chars to start at
     * @param end  the position in set of chars to end before
     * @param letters  only allow letters?
     * @param numbers  only allow numbers?
     * @param chars  the set of chars to choose randoms from.
     *  If {@code null}, then it will use the set of all chars.
     * @return the random string
     * @throws ArrayIndexOutOfBoundsException if there are not
     *  {@code (end - start) + 1} characters in the set array.
     */
    public static String random(final int count, final int start, final int end, final boolean letters, final boolean numbers, final char... chars) {
        return random(count, start, end, letters, numbers, chars, RANDOM);
    }

    /**
     * <p>Creates a random string based on a variety of options, using
     * supplied source of randomness.</p>
     *
     * <p>If start and end are both {@code 0}, start and end are set
     * to {@code ' '} and {@code 'z'}, the ASCII printable
     * characters, will be used, unless letters and numbers are both
     * {@code false}, in which case, start and end are set to
     * {@code 0} and {@link Character#MAX_CODE_POINT}.
     *
     * <p>If set is not {@code null}, characters between start and
     * end are chosen.</p>
     *
     * <p>This method accepts a user-supplied {@link Random}
     * instance to use as a source of randomness. By seeding a single
     * {@link Random} instance with a fixed seed and using it for each call,
     * the same random sequence of strings can be generated repeatedly
     * and predictably.</p>
     *
     * @param count  the length of random string to create
     * @param start  the position in set of chars to start at (inclusive)
     * @param end  the position in set of chars to end before (exclusive)
     * @param letters  only allow letters?
     * @param numbers  only allow numbers?
     * @param chars  the set of chars to choose randoms from, must not be empty.
     *  If {@code null}, then it will use the set of all chars.
     * @param random  a source of randomness.
     * @return the random string
     * @throws ArrayIndexOutOfBoundsException if there are not
     *  {@code (end - start) + 1} characters in the set array.
     * @throws IllegalArgumentException if {@code count} &lt; 0 or the provided chars array is empty.
     * @since 2.0
     */
    public static String random(int count, int start, int end, final boolean letters, final boolean numbers,
                                final char[] chars, final Random random) {
        if (count == 0) {
            return "";
        } else if (count < 0) {
            throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
        }
        if (chars != null && chars.length == 0) {
            throw new IllegalArgumentException("The chars array must not be empty");
        }

        if (start == 0 && end == 0) {
            if (chars != null) {
                end = chars.length;
            } else {
                if (!letters && !numbers) {
                    end = Character.MAX_CODE_POINT;
                } else {
                    end = 'z' + 1;
                    start = ' ';
                }
            }
        } else {
            if (end <= start) {
                throw new IllegalArgumentException("Parameter end (" + end + ") must be greater than start (" + start + ")");
            }
        }

        final int zero_digit_ascii = 48;
        final int first_letter_ascii = 65;

        if (chars == null && (numbers && end <= zero_digit_ascii
                || letters && end <= first_letter_ascii)) {
            throw new IllegalArgumentException("Parameter end (" + end + ") must be greater then (" + zero_digit_ascii + ") for generating digits " +
                    "or greater then (" + first_letter_ascii + ") for generating letters.");
        }

        final StringBuilder builder = new StringBuilder(count);
        final int gap = end - start;

        while (count-- != 0) {
            int codePoint;
            if (chars == null) {
                codePoint = random.nextInt(gap) + start;

                switch (Character.getType(codePoint)) {
                    case Character.UNASSIGNED:
                    case Character.PRIVATE_USE:
                    case Character.SURROGATE:
                        count++;
                        continue;
                }

            } else {
                codePoint = chars[random.nextInt(gap) + start];
            }

            final int numberOfChars = Character.charCount(codePoint);
            if (count == 0 && numberOfChars > 1) {
                count++;
                continue;
            }

            if (letters && Character.isLetter(codePoint)
                    || numbers && Character.isDigit(codePoint)
                    || !letters && !numbers) {
                builder.appendCodePoint(codePoint);

                if (numberOfChars == 2) {
                    count--;
                }

            } else {
                count++;
            }
        }
        return builder.toString();
    }


    /**
     * <p>Creates a random string whose length is the number of characters
     * specified.</p>
     *
     * <p>Characters will be chosen from the set of characters
     * specified by the string, must not be empty.
     * If null, the set of all characters is used.</p>
     *
     * @param count  the length of random string to create
     * @param chars  the String containing the set of characters to use,
     *  may be null, but must not be empty
     * @return the random string
     * @throws IllegalArgumentException if {@code count} &lt; 0 or the string is empty.
     */
    public static String random(final int count, final String chars) {
        if (chars == null) {
            return random(count, 0, 0, false, false, null, RANDOM);
        }
        return random(count, chars.toCharArray());
    }

    /**
     * <p>Creates a random string whose length is the number of characters
     * specified.</p>
     *
     * <p>Characters will be chosen from the set of characters specified.</p>
     *
     * @param count  the length of random string to create
     * @param chars  the character array containing the set of characters to use,
     *  may be null
     * @return the random string
     * @throws IllegalArgumentException if {@code count} &lt; 0.
     */
    public static String random(final int count, final char... chars) {
        if (chars == null) {
            return random(count, 0, 0, false, false, null, RANDOM);
        }
        return random(count, 0, chars.length, false, false, chars, RANDOM);
    }
}


/**
 * @author huanxl
 * @date 2023/5/9 18:28
 */
class RandomUtil {
    private static final Random RANDOM = new SecureRandom();

    /**
     * <p>
     * {@code RandomUtils} instances should NOT be constructed in standard
     * programming. Instead, the class should be used as
     * {@code RandomUtils.nextBytes(5);}.
     * </p>
     *
     * <p>
     * This constructor is public to permit tools that require a JavaBean
     * instance to operate.
     * </p>
     */
    public RandomUtil() {
        super();
    }

    /**
     * <p>
     * Returns a random boolean value
     * </p>
     *
     * @return the random boolean
     * @since 3.5
     */
    public static boolean nextBoolean() {
        return RANDOM.nextBoolean();
    }

    /**
     * <p>
     * Creates an array of random bytes.
     * </p>
     *
     * @param count
     *            the size of the returned array
     * @return the random byte array
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public static byte[] nextBytes(final int count) {

        final byte[] result = new byte[count];
        RANDOM.nextBytes(result);
        return result;
    }

    /**
     * <p>
     * Returns a random integer within the specified range.
     * </p>
     *
     * @param startInclusive
     *            the smallest value that can be returned, must be non-negative
     * @param endExclusive
     *            the upper bound (not included)
     * @throws IllegalArgumentException
     *             if {@code startInclusive > endExclusive} or if
     *             {@code startInclusive} is negative
     * @return the random integer
     */
    public static int nextInt(final int startInclusive, final int endExclusive) {

        if (startInclusive == endExclusive) {
            return startInclusive;
        }

        return startInclusive + RANDOM.nextInt(endExclusive - startInclusive);
    }

    /**
     * <p> Returns a random int within 0 - Integer.MAX_VALUE </p>
     *
     * @return the random integer
     * @see #nextInt(int, int)
     * @since 3.5
     */
    public static int nextInt() {
        return nextInt(0, Integer.MAX_VALUE);
    }

    /**
     * <p>
     * Returns a random long within the specified range.
     * </p>
     *
     * @param startInclusive
     *            the smallest value that can be returned, must be non-negative
     * @param endExclusive
     *            the upper bound (not included)
     * @throws IllegalArgumentException
     *             if {@code startInclusive > endExclusive} or if
     *             {@code startInclusive} is negative
     * @return the random long
     */
    public static long nextLong(final long startInclusive, final long endExclusive) {

        if (startInclusive == endExclusive) {
            return startInclusive;
        }

        return (long) nextDouble(startInclusive, endExclusive);
    }

    /**
     * <p> Returns a random long within 0 - Long.MAX_VALUE </p>
     *
     * @return the random long
     * @see #nextLong(long, long)
     * @since 3.5
     */
    public static long nextLong() {
        return nextLong(0, Long.MAX_VALUE);
    }

    /**
     * <p>
     * Returns a random double within the specified range.
     * </p>
     *
     * @param startInclusive
     *            the smallest value that can be returned, must be non-negative
     * @param endExclusive
     *            the upper bound (not included)
     * @throws IllegalArgumentException
     *             if {@code startInclusive > endExclusive} or if
     *             {@code startInclusive} is negative
     * @return the random double
     */
    public static double nextDouble(final double startInclusive, final double endExclusive) {

        if (startInclusive == endExclusive) {
            return startInclusive;
        }

        return startInclusive + ((endExclusive - startInclusive) * RANDOM.nextDouble());
    }

    /**
     * <p> Returns a random double within 0 - Double.MAX_VALUE </p>
     *
     * @return the random double
     * @see #nextDouble(double, double)
     * @since 3.5
     */
    public static double nextDouble() {
        return nextDouble(0, Double.MAX_VALUE);
    }

    /**
     * <p>
     * Returns a random float within the specified range.
     * </p>
     *
     * @param startInclusive
     *            the smallest value that can be returned, must be non-negative
     * @param endExclusive
     *            the upper bound (not included)
     * @throws IllegalArgumentException
     *             if {@code startInclusive > endExclusive} or if
     *             {@code startInclusive} is negative
     * @return the random float
     */
    public static float nextFloat(final float startInclusive, final float endExclusive) {

        if (startInclusive == endExclusive) {
            return startInclusive;
        }

        return startInclusive + ((endExclusive - startInclusive) * RANDOM.nextFloat());
    }

    /**
     * <p> Returns a random float within 0 - Float.MAX_VALUE </p>
     *
     * @return the random float
     * @see #nextFloat(float, float)
     * @since 3.5
     */
    public static float nextFloat() {
        return nextFloat(0, Float.MAX_VALUE);
    }
}
